using System;
using Fadd.Globalization;

#if TEST
using Xunit;
#endif


namespace Fadd.Validation
{
    /// <summary>
    /// Specifies a max length.
    /// </summary>
    /// <remarks>
    /// On a string it specified maximum number of letters, while on int it specifies the max number.
    /// </remarks>
    /// <para>
    /// Language file items:
    /// <list type="table">
    ///     <listheader>
    ///         <term>ItemName</term>
    ///         <description>Language text</description>
    ///     </listheader>
    ///     <item>
    ///         <term>Max</term>
    ///         <description>'{0}' must be less or equal to {1}.</description>
    ///     </item>
    ///     <item>
    ///         <term>MaxString</term>
    ///         <description>'{0}' must be contain {1} or less characters.</description>
    ///     </item>
    /// </list>
    /// </para>
    [AttributeUsage(AttributeTargets.Method|AttributeTargets.Property|AttributeTargets.Parameter)]
    public class ValidateMaxAttribute : ValidateAttribute
    {
        private readonly object _max;
        private object _value;

        private const string DefaultMessage = "'{0}' must be less or equal to {1}.";
        private const string DefaultMessageString = "'{0}' must be {1} characters or less.";

        /// <summary>
        /// Initializes a new instance of the <see cref="ValidateMaxAttribute"/> class.
        /// </summary>
        /// <param name="max">max length. should match the type being validated, with the exception of string
        /// where the type should be int.</param>
        public ValidateMaxAttribute(object max)
        {
            _max = max;
        }

        /// <summary>
        /// Max length
        /// </summary>
        public object Max
        {
            get { return _max; }
        }

        /// <summary>
        /// Validate value
        /// </summary>
        /// <param name="context">Can be used to send a context object to the validation class. Useful if you provide your own validation classes which need to get information from your application. <seealso cref="BeforeValidationEventArgs"/></param>
        /// <param name="value">value to validate</param>
        /// <returns>
        /// true if value passed the validation; otherwise false.
        /// </returns>
        /// <exception cref="NotSupportedException">If the specified object is not supported.</exception>
        /// <remarks>
        /// Supports basetypes and all types that implements IComparable (passing an int)
        /// </remarks>
        public override bool Validate(object context, object value)
        {
            if (IsEmpty(value))
                return true;

            _value = value;
            if (value is string)
            {
                string s = (string)value;
                return s.Length <= (int)_max;
            }

            IComparable comparable = value as IComparable;
            if (comparable != null)
                return comparable.CompareTo(_max) <= 0;


            throw new NotSupportedException(GetType() + " do not support " + value.GetType());
        }

        /// <summary>
        /// Localize error if validation failed.
        /// </summary>
        /// <param name="fieldName">Localized field name.</param>
        /// <param name="validationLanguage">Language node with all validation translations.</param>
        /// <returns>A localized error message if the validation failed; otherwise null.</returns>
        /// <example>
        /// <code>
        /// attribute.Localize("FirstName", "'{0}' is required"); 
        /// // will return "'{0}' is required" if the validation failed, else null.
        /// </code>
        /// </example>
        public override string Format(string fieldName, ILanguageNode validationLanguage)
        {
            string format;
            if (validationLanguage == null)
                format = _value is string ? DefaultMessageString : DefaultMessage;
            else
                format = _value is string ? validationLanguage["MaxString"] : validationLanguage["Max"];

            return string.Format(format, fieldName, _max);
        }

        /// <summary>
        /// Determines if the validation class support the specified type.
        /// </summary>
        /// <param name="type">Property/Value type.</param>
        /// <returns>true if the type is supported.</returns>
        public override bool SupportsType(Type type)
        {
            return type.GetInterface("IComparable", false) != null;
        }

#if TEST
        [Fact]
        private static void TestValidate()
        {
            ValidateMaxAttribute attribute = new ValidateMaxAttribute(10);
            Assert.True(attribute.Validate(null, 1));
            Assert.True(attribute.Validate(null, 6));
            Assert.True(attribute.Validate(null, "Tjena"));
            Assert.False(attribute.Validate(null, 11));
            Assert.False(attribute.Validate(null, "Hejsan alla barn!"));

            attribute = new ValidateMaxAttribute(10.0);
            Assert.False(attribute.Validate(null, 10.1));
            Assert.True(attribute.Validate(null, 6.0));
        }

        [Fact]
        private static void TestLanguage()
        {
            MemLanguageNode language = new MemLanguageNode(1033, "Validation");
            language.Add("Max", 1033, "{0}={1}.");
            language.Add("MaxString", 1033, "{0}-{1}.");
            ValidateMaxAttribute max = new ValidateMaxAttribute(5);
            Assert.Equal("Age=5.", max.Format("Age", language));
            max.Validate(null, "hej");
            Assert.Equal("FirstName-5.", max.Format("FirstName", language));

            Assert.Equal(string.Format(DefaultMessageString, "FirstName", 5), max.Format("FirstName", null));
            max.Validate(null, 3);
            Assert.Equal(string.Format(DefaultMessage, "Age", 5), max.Format("Age", null));
        }
#endif
    }
}
